// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.asm;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.LocalVariablesSorter;
import org.objectweb.asm.tree.ClassNode;

import com.dustedpixels.asm.core.Method;


/**
 * Inlines methods, but does not remove them.
 * 
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class PrivateMethodInliner extends ClassAdapter {

  private final MethodSupplier inlinedMethodSupplier;
  private String owner;

  /**
   * Initializes this visitor with underlying components.
   * 
   * @param out Output visitor
   * @param inlinedMethodSupplier Supplier of the methods to inline.
   */
  public PrivateMethodInliner(ClassVisitor out, MethodSupplier inlinedMethodSupplier) {
    super(new ClassNode());
    this.inlinedMethodSupplier = inlinedMethodSupplier;
  }

  @Override
  public void visit(
      int version, 
      int access, 
      String name, 
      String signature, 
      String superName,
      String[] interfaces) {

    this.owner = name;
    super.visit(version, access, name, signature, superName, interfaces);
  }

  @Override
  public MethodVisitor visitMethod(
      int access, 
      String name, 
      String desc, 
      String signature,
      String[] exceptions) {

    MethodVisitor out = super.visitMethod(access, name, desc, signature, exceptions);
    
    if (out != null) {
      out = new MethodInliner(out, owner, access, name, desc, inlinedMethodSupplier);
    }
    
    return out;
  }
  
  /**
   * Inlines local methods code in place of method invocation.
   */
  public final class MethodInliner extends MethodAdapter {
    private final LocalVariablesSorter out;
    private final String owner;
    private final MethodSupplier methodSupplier;
    
    /**
     * Initializes this visitor with underlying components.
     * 
     * @param out Output visitor.
     * @param owner An owner of this method.
     * @param access Access rights for visited method.
     * @param name Name of visited method.
     * @param desc Descriptor of visited method.
     * @param inlinedMethodSupplier Supplier of the methods to inline.
     */
    public MethodInliner(
        MethodVisitor out, 
        String owner, 
        int access, 
        String name, 
        String desc, 
        MethodSupplier inlinedMethodSupplier) {
      
      super(new LocalVariablesSorter(access, desc, out));
      this.out = (LocalVariablesSorter) mv;
      this.owner = owner;
      this.methodSupplier = inlinedMethodSupplier;
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
      boolean wasInlined = false;
      
      if (opcode == Opcodes.INVOKESPECIAL && owner.equals(this.owner)) {
        Method method = methodSupplier.get(name, desc);
        if (method != null) {
          boolean isStatic = (opcode == Opcodes.INVOKESTATIC);
          CodeInliner inliner = new CodeInliner(out, isStatic, desc);
          method.accept(inliner);
        }
      }
        
      if (!wasInlined) {
        super.visitMethodInsn(opcode, owner, name, desc);
      }
    }
  }
}
